/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package csci6450.chickenFriedFS.nuggetServer.threads;

import csci6450.chickenFriedFS.nuggetCore.Nugget;
import csci6450.chickenFriedFS.nuggetCore.NuggetConstants;
import csci6450.chickenFriedFS.nuggetCore.NuggetServerInfo;
import csci6450.chickenFriedFS.nuggetCore.NuggetUtilities;
import csci6450.chickenFriedFS.nuggetServer.NuggetFileStoreManager;
import csci6450.chickenFriedFS.nuggetServer.NuggetServer;
import csci6450.chickenFriedFS.nuggetServer.NuggetServerLog;
import csci6450.chickenFriedFS.nuggetServer.RuntimeSettings;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 *
 * @author jacobschoen
 */
public class NuggetServerClientListener extends Thread {

    int runningThreads = 0;
    /**
     * Socket for this server to run on.
     */
    private ServerSocket clientRequestServer;

    public NuggetServerClientListener() throws IOException {
        this.clientRequestServer = new ServerSocket(RuntimeSettings.getInstance().getClientListenerPort());
        System.out.println("Server listening for clients on port " + RuntimeSettings.getInstance().getClientListenerPort() + ".");
    }

    @Override
    public void run() {
        Socket client = null;
        while (true) {
            try {
                client = this.clientRequestServer.accept();
                Connect c = new Connect(client);
                c.start();
            } catch (Exception e) {
                e.printStackTrace();
                if (client != null) {
                    try {
                        client.close();
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                }
            }
        }
    }

    public synchronized void updateThreadCount(int i) {
        this.runningThreads = this.runningThreads + 1;
    }

    class Connect extends Thread {

        private Socket client = null;
        private ObjectInputStream inputStream = null;
        private ObjectOutputStream outputStream = null;
        private ServerSocket socket = null;
        private Integer port = NuggetServer.getInstance().STARTING_PORT;

        public Connect(Socket clientSocket) {
            this.client = clientSocket;
            try {
                this.inputStream = new ObjectInputStream(this.client.getInputStream());
                this.outputStream = new ObjectOutputStream(this.client.getOutputStream());
            } catch (Exception e1) {
                try {
                    this.client.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void run() {
            try {
                updateThreadCount(1);
                boolean done = false;
                while (!done) {
                    done = true;
                    try {
                        socket = new ServerSocket(port);
                    } catch (Exception e) {
                        done = false;
                        port = port + 1;
                    }
                }
                done = false;
                System.out.println("Getting function");
                String function = (String) this.inputStream.readObject();
                System.out.println("Function :="+function);
                if (function.equals(NuggetConstants.PUT)) {
                    this.put();
                } else if (function.startsWith(NuggetConstants.GET)) {
                    this.get(function.substring(NuggetConstants.GET.length() + 1));
                } else if (function.equals(NuggetConstants.RM)) {
                    this.remove();
                } else if (function.equals(NuggetConstants.FORMAT)) {
                    NuggetFileStoreManager.getInstance().format();
                    NuggetServerLog.getInstance().format();
                    this.outputStream.writeObject(NuggetConstants.SUCCESS);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (this.client != null) {
                    try {
                        this.client.close();
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                    updateThreadCount(1);
                    this.interrupt();
                }
            }
        }

        private void put() throws IOException, Exception {
            System.out.println("Inside put");
            //open socket on this port to get the bytes
            Socket fileClient = null;
            try {
                boolean done = false;
                //sends the port number for the client to use to upload file.
                this.outputStream.writeObject(port);
                // close streams and connections
                //open the socket to get the file
                this.client.close();
                Nugget nugget = null;
                fileClient = socket.accept();
                System.out.println("Put Running threads :=" + runningThreads);
                NuggetUtilities.memCheck(runningThreads);
                ObjectInputStream ois = new ObjectInputStream(fileClient.getInputStream());
                nugget = (Nugget) ois.readObject();

                if (nugget != null) {
                    this.outputStream = new ObjectOutputStream(fileClient.getOutputStream());
                    try {
                        long offset = NuggetFileStoreManager.getInstance().addNugget(nugget.getNugget());
                        if (offset < 0) {
                            //this looks stupid, but is a hack since for some reason it never gets the
                            //offset right for the first chunk sent. But it works
                            offset = NuggetFileStoreManager.getInstance().addNugget(nugget.getNugget());

                        }

                        nugget.setNuggetOffset(offset);


                        //tell the client it is all good.
                        this.outputStream.writeObject(this.replicate(nugget));

//                        this.outputStream.writeObject(NuggetConstants.SUCCESS);
                        this.outputStream.flush();
                    } catch (Exception exception) {
                        exception.printStackTrace();
                        this.outputStream.writeObject(NuggetConstants.FAILURE);
                        this.outputStream.flush();
                    }
                } else {
                    this.outputStream.writeObject(NuggetConstants.FAILURE);
                    this.outputStream.flush();
                }
                done = true;

            } catch (IOException iOException) {
                iOException.printStackTrace();
                throw iOException;
            } catch (Exception e) {
                e.printStackTrace();
                throw e;
            } finally {
                if (fileClient != null) {
                    fileClient.close();
                }
                if (socket != null) {
                    socket.close();
                }
            }
        }

        private void get(String function) throws IOException, Exception {
            Socket fileClient = null;
            try {
                long offset = Long.parseLong(function);
                //open socket to send the nugget over
                boolean done = false;
                //sends the port number for the client to use to upload file.
                this.outputStream.writeObject(port);
                //open the socket to get the file
                this.client.close();
                fileClient = socket.accept();
                System.out.println("Get Running threads :=" + runningThreads);
                NuggetUtilities.memCheck(runningThreads * 2);
                this.outputStream = new ObjectOutputStream(fileClient.getOutputStream());
                byte[] bytes = NuggetFileStoreManager.getInstance().getNugget(offset);
                Nugget temp = new Nugget(bytes);
                this.outputStream.writeObject(temp);
            } catch (IOException iOException) {
                throw iOException;
            } catch (Exception e) {
                throw e;
            } finally {
                if (fileClient != null) {
                    fileClient.close();
                }
                if (socket != null) {
                    socket.close();
                }
            }
        }

        private void remove() throws IOException, ClassNotFoundException, Exception {
            //open socket to send the nugget over
            Socket fileClient = null;
            try {
                //sends the port number for the client to use to upload file.
                this.outputStream.writeObject(port);
                //open the socket to get the file
                this.client.close();
                fileClient = socket.accept();
                this.inputStream = new ObjectInputStream(fileClient.getInputStream());
                boolean success = false;
                Nugget nugget = (Nugget) this.inputStream.readObject();
                if (nugget != null) {
                    this.outputStream = new ObjectOutputStream(fileClient.getOutputStream());
                    success = NuggetFileStoreManager.getInstance().deleteNugget(nugget.getNuggetOffset());
                    if (success) {
                        success = NuggetServerLog.getInstance().removeNugget(nugget);
                    }
                }

                String message = (success ? NuggetConstants.SUCCESS : NuggetConstants.FAILURE);
                this.outputStream.writeObject(message);
            } catch (IOException iOException) {
                throw iOException;
            } catch (ClassNotFoundException classNotFoundException) {
                throw classNotFoundException;
            } catch (Exception e) {
                throw e;
            } finally {
                if (fileClient != null) {
                    fileClient.close();
                }
                if (socket != null) {
                    socket.close();
                }
            }

        }

        private String replicate(Nugget nugget) throws IOException, ClassNotFoundException {
            Socket repSocket = null;
            try {
                repSocket = new Socket(RuntimeSettings.getInstance().getMetaServerIp(), RuntimeSettings.getInstance().getReplicationManagerPort());

                ObjectOutputStream out = new ObjectOutputStream(repSocket.getOutputStream());
                java.net.InetAddress i = java.net.InetAddress.getLocalHost();


                out.writeObject(i.getHostAddress());
                ObjectInputStream input = new ObjectInputStream(repSocket.getInputStream());

                NuggetServerInfo info = (NuggetServerInfo) input.readObject();

                repSocket.close();
                if (info != null) {

                    nugget.setNuggetRepLocation(info.getIpAddress());
                    nugget.setNuggetRepPort(info.getClientPort());


                    repSocket = new Socket(info.getIpAddress(), info.getReplicationPort());
                    out = new ObjectOutputStream(repSocket.getOutputStream());

                    input = new ObjectInputStream(repSocket.getInputStream());
                    Integer repPort = (Integer) input.readObject();

                    repSocket.close();

                    repSocket = new Socket(info.getIpAddress(), repPort);

                    out = new ObjectOutputStream(repSocket.getOutputStream());
                    out.writeObject(nugget);

                    input = new ObjectInputStream(repSocket.getInputStream());
                    String response = (String) input.readObject();

                    if (response.equals(NuggetConstants.SUCCESS)) {

                        NuggetServerLog.getInstance().addNugget(nugget);
                        return NuggetConstants.SUCCESS;
                    } else {

                        return NuggetConstants.FAILURE;
                    }
                } else {

                    return NuggetConstants.FAILURE;
                }
            } catch (ClassNotFoundException ex) {
                ex.printStackTrace();
                throw ex;
            } catch (UnknownHostException ex) {
                ex.printStackTrace();
                throw ex;
            } catch (IOException ex) {
                ex.printStackTrace();
                throw ex;
            } finally {
                if (repSocket != null) {
                    repSocket.close();
                }

            }
        }
    }
}
